Chapter 3

Flutter Widgets and Architecture

Session 3

Learning Objectives

By the end of this chapter, you will be able to:

1

Flutter's Widget-First Architecture

Flutter's architecture is fundamentally different from traditional UI frameworks. Understanding this concept is crucial for effective Flutter development.

Core Principles

  • Everything is a widget: Layout, styling, and even gesture handling are widgets.
  • Immutable descriptions: Widgets are lightweight immutable descriptions of part of the UI; the framework builds and updates RenderObjects under the hood.
  • Composition over mutation: UI is built by composing widgets into a tree. Updating UI means creating new widget instances — Flutter diffs the tree and efficiently updates the rendered output.

Key idea: Think in terms of composition, not mutation of UI elements.

2

Widget Tree and Composition

Understanding the widget tree structure helps you organize your Flutter applications effectively.

Widget Tree Structure

  • Root widget: Typically MaterialApp (Material design) or CupertinoApp (iOS style).
  • Scaffold: Each screen commonly uses Scaffold (app bar, body, floating action button).
  • Nesting: Widgets nest: parent widgets provide layout and behavior to children. Keep widget trees readable by extracting subtrees into their own widgets.

Practical rule: One file per major widget or screen for clarity; small helper widgets inline when simple.

3

StatelessWidget vs StatefulWidget

Choosing the right widget type is fundamental to building efficient Flutter applications.

StatelessWidget

Use when UI depends only on constructor parameters and never changes during the widget's lifetime. They are cheap and rebuild frequently without preserving state.

StatefulWidget

Use when UI depends on mutable state that changes over time (user input, timers, network results). A StatefulWidget has a State object where mutable fields live and setState() triggers rebuilds.

Example Pattern (Stateless)

class GreetingCard extends StatelessWidget {
  final String name;
  const GreetingCard({Key? key, required this.name}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text('Hello, $name');
  }
}

Example Pattern (Stateful)

class CounterPage extends StatefulWidget {
  const CounterPage({Key? key}) : super(key: key);

  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State {
  int _count = 0;

  void _increment() => setState(() => _count++);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(onPressed: _increment, child: Text('Increment')),
      ],
    );
  }
}

Decision Checklist

  • If you need to call setState() or hold references to controllers (TextEditingController, AnimationController), use StatefulWidget.
  • Prefer moving state up (lifting state) to avoid duplicating state across widgets.
4

Common Layout Widgets and Their Uses

Flutter provides a rich set of layout widgets. Understanding when to use each is key to building effective UIs.

Container

Single-child box with padding, margin (via Padding/Align), background, border, size constraints. Use sparingly; prefer dedicated widgets (Padding, SizedBox) for clarity.

Row and Column

Linear layouts horizontal and vertical. Use mainAxisAlignment, crossAxisAlignment, and mainAxisSize.

Expanded/Flexible

Make children fill available space in Row/Column. Expanded forces fill; Flexible gives flexibility.

SizedBox

Fixed spacing or empty space.

Padding

Adds internal spacing.

Align/Center

Position a child within available space.

Stack

Overlap children; use Positioned for explicit placement.

ListView

Scrollable linear list (use builder for large lists).

GridView

Grid layout for items.

Column pitfalls: Avoid unbounded height errors by wrapping scrollable content in Expanded or using SingleChildScrollView.

Example Snippet: Profile Card Layout

Card(
  child: Padding(
    padding: const EdgeInsets.all(16),
    child: Row(
      children: [
        CircleAvatar(radius: 30, child: Icon(Icons.person)),
        SizedBox(width: 12),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text('Adu Amankwah', style: Theme.of(context).textTheme.subtitle1),
              Text('Flutter student', style: Theme.of(context).textTheme.bodyText2),
            ],
          ),
        ),
      ],
    ),
  ),
)
5

Alignment, Padding, Margin, and Constraints

Understanding spacing and constraints is essential for creating well-laid-out Flutter applications.

Spacing Concepts

  • Padding: Space inside a widget (use Padding or Container.padding).
  • Margin: Outside spacing — achieved with parent widgets like Padding or SizedBox around the child.
  • Alignment: Positions children inside a parent (e.g., Align(alignment: Alignment.centerRight)).

Constraints Flow

Constraints flow down: parents tell children their constraints (min/max width/height); children choose size and tell parents their size. Understand constraints to avoid layout exceptions (e.g., "RenderBox was not laid out" or unbounded height inside Column).

Practical tip: When a vertical Column contains a scrollable ListView, wrap the ListView in Expanded or set shrinkWrap: true and avoid nesting scrollables unnecessarily.

6

Text, Images, and Assets

  • Text styling via TextStyle and themes. Prefer using Theme.of(context) to keep app consistent.
  • Images: Image.asset for packaged assets, Image.network for remote images, FadeInImage for smooth placeholders. Add images to pubspec.yaml under assets:.
  • Iconography: Icon widget with Icons.* or custom icon fonts.

Assets Example (pubspec.yaml excerpt)

flutter:
  assets:
    - assets/images/profile.png
7

Widget Lifecycle Basics (StatefulWidget)

Important Lifecycle Callbacks in State

  • initState() — called once when the state object is first created; initialize controllers, start listeners.
  • didChangeDependencies() — called after initState and when dependencies change (e.g., InheritedWidget updates).
  • build() — called often; must be pure and fast.
  • didUpdateWidget(Widget oldWidget) — when parent widget rebuilds with new configuration.
  • dispose() — clean up controllers, listeners, timers to avoid leaks.

Rule: Heavy initialization should be in initState(), and always dispose() controllers.

Example

@override
void initState() {
  super.initState();
  _controller = TextEditingController();
}

@override
void dispose() {
  _controller.dispose();
  super.dispose();
}
8

Performance Tips and Best Practices

  • Minimize work inside build(); avoid heavy computations — move them to model or compute once and cache.
  • Use const constructors (const Text('Hello')) where possible to reduce rebuild costs.
  • Extract subtrees into separate widgets so only those widgets rebuild when their inputs change.
  • Use ListView.builder for long or infinite lists to build items lazily.
  • Avoid setState on large widgets; scope state to the smallest subtree necessary.
9

Interactivity: Gestures and Buttons

  • Buttons: ElevatedButton, TextButton, IconButton, OutlinedButton. Use onPressed to handle taps.
  • Gesture detectors: GestureDetector for low-level events (tap, long press, drag) when a ready widget is not available.
  • Forms: Form + TextFormField + FormState for validated inputs.

Example Button

ElevatedButton(
  onPressed: () { /* handle */ },
  child: Text('Save'),
)
10

Accessibility and Responsiveness (Short)

  • Use semantic labels (Semantics) for non-text UI elements and alt information for images.
  • Make touch targets large enough (44–48 px recommended).
  • Use LayoutBuilder or MediaQuery for responsive layouts; test on multiple device sizes and orientations.
11

Small Guided Build: Simple Profile Screen

Goal

Compose a profile screen showing avatar, name, bio, and a follow button with counter.

Structure

  • Scaffold → AppBar
  • Body → Column with top Card (avatar + name + bio) and follow button that increments state

Key Points to Implement

  • Use StatefulWidget to hold follow count
  • Extract profile card into a StatelessWidget that accepts parameters
  • Use padding, Row, CircleAvatar, Expanded, and ElevatedButton
12

Exercises

1. Build a responsive profile screen

Avatar (local asset), name, role, and a short bio. Follow button increments a counter and updates label to "Following (n)".

2. Layout practice

Create a grid of colored tiles using GridView.count with 3 columns and spacing.

3. ListView builder

Create a list of 100 generated names and display them with separators; tapping an item shows a SnackBar with the name.

4. Form practice

Build a simple login form with TextFormField for email and password and basic validation (non-empty, email contains @).

13

Session Assignment

Implement exercise 1 (profile screen) and exercise 4 (login form). Ensure TextEditingControllers are disposed correctly. Add comments describing why you chose Stateless vs Stateful for each widget. Submit source code and short screenshots or run logs.